// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import <UIKit/UIKit.h>

#import "ios/chrome/browser/ui/bubble/bubble_view.h"
#import "ios/chrome/browser/ui/bubble/bubble_view_controller.h"
#import "ios/chrome/browser/ui/bubble/bubble_view_controller_presenter.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"

#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif

@interface BubbleViewControllerPresenter (Testing)

// Underlying bubble view controller managed by the presenter.
@property(nonatomic, strong) BubbleViewController* bubbleViewController;

// Underlying timer used to dismiss the bubble automatically.
@property(nonatomic, strong) NSTimer* bubbleDismissalTimer;

// Underlying timer used to mark the user as no longer engaged automatically.
@property(nonatomic, strong) NSTimer* engagementTimer;

@end

// Test fixture to test the BubbleViewControllerPresenter.
class BubbleViewControllerPresenterTest : public PlatformTest {
 public:
  BubbleViewControllerPresenterTest()
      : bubbleViewControllerPresenter_([[BubbleViewControllerPresenter alloc]
                 initWithText:@"Text"
               arrowDirection:BubbleArrowDirectionUp
                    alignment:BubbleAlignmentCenter
            dismissalCallback:^{
              dismissalCallbackCount_++;
            }]),
        window_([[UIWindow alloc]
            initWithFrame:CGRectMake(0.0, 0.0, 500.0, 500.0)]),
        parentViewController_([[UIViewController alloc] init]),
        anchorPoint_(CGPointMake(250.0, 250.0)),
        dismissalCallbackCount_(0) {
    parentViewController_.view.frame = CGRectMake(0.0, 0.0, 500.0, 500.0);
    [window_ addSubview:parentViewController_.view];
  }

 protected:
  // The presenter object under test.
  BubbleViewControllerPresenter* bubbleViewControllerPresenter_;
  // The window the |parentViewController_|'s view is in.
  // -presentInViewController: expects the |anchorPoint| parameter to be in
  // window coordinates, which requires the |view| property to be in a window.
  UIWindow* window_;
  // The view controller the BubbleViewController is added as a child of.
  UIViewController* parentViewController_;
  // The point at which the bubble is anchored.
  CGPoint anchorPoint_;
  // How many times |bubbleViewControllerPresenter_|'s internal
  // |dismissalCallback| has been invoked. Defaults to 0. Every time the
  // callback is invoked, |dismissalCallbackCount_| increments.
  int dismissalCallbackCount_;
};

// Tests that, after initialization, the internal BubbleViewController and
// BubbleView have not been added to the parent.
TEST_F(BubbleViewControllerPresenterTest, InitializedNotAdded) {
  EXPECT_FALSE([parentViewController_.childViewControllers
      containsObject:bubbleViewControllerPresenter_.bubbleViewController]);
  EXPECT_FALSE([parentViewController_.view.subviews
      containsObject:bubbleViewControllerPresenter_.bubbleViewController.view]);
}

// Tests that -presentInViewController: adds the BubbleViewController and
// BubbleView to the parent.
TEST_F(BubbleViewControllerPresenterTest, PresentAddsToViewController) {
  [bubbleViewControllerPresenter_
      presentInViewController:parentViewController_
                         view:parentViewController_.view
                  anchorPoint:anchorPoint_];
  EXPECT_TRUE([parentViewController_.childViewControllers
      containsObject:bubbleViewControllerPresenter_.bubbleViewController]);
  EXPECT_TRUE([parentViewController_.view.subviews
      containsObject:bubbleViewControllerPresenter_.bubbleViewController.view]);
}

// Tests that initially the dismissal callback has not been invoked.
TEST_F(BubbleViewControllerPresenterTest, DismissalCallbackCountInitialized) {
  EXPECT_EQ(0, dismissalCallbackCount_);
}

// Tests that presenting the bubble but not dismissing it does not invoke the
// dismissal callback.
TEST_F(BubbleViewControllerPresenterTest, DismissalCallbackNotCalled) {
  [bubbleViewControllerPresenter_
      presentInViewController:parentViewController_
                         view:parentViewController_.view
                  anchorPoint:anchorPoint_];
  EXPECT_EQ(0, dismissalCallbackCount_);
}

// Tests that presenting then dismissing the bubble invokes the dismissal
// callback.
TEST_F(BubbleViewControllerPresenterTest, DismissalCallbackCalledOnce) {
  [bubbleViewControllerPresenter_
      presentInViewController:parentViewController_
                         view:parentViewController_.view
                  anchorPoint:anchorPoint_];
  [bubbleViewControllerPresenter_ dismissAnimated:NO];
  EXPECT_EQ(1, dismissalCallbackCount_);
}

// Tests that calling -dismissAnimated: after the bubble has already been
// dismissed does not invoke the dismissal callback again.
TEST_F(BubbleViewControllerPresenterTest, DismissalCallbackNotCalledTwice) {
  [bubbleViewControllerPresenter_
      presentInViewController:parentViewController_
                         view:parentViewController_.view
                  anchorPoint:anchorPoint_];
  [bubbleViewControllerPresenter_ dismissAnimated:NO];
  [bubbleViewControllerPresenter_ dismissAnimated:NO];
  EXPECT_EQ(1, dismissalCallbackCount_);
}

// Tests that calling -dismissAnimated: before the bubble has been presented
// does not invoke the dismissal callback.
TEST_F(BubbleViewControllerPresenterTest,
       DismissalCallbackNotCalledBeforePresentation) {
  [bubbleViewControllerPresenter_ dismissAnimated:NO];
  EXPECT_EQ(0, dismissalCallbackCount_);
}

// Tests that the timers are |nil| before the bubble is presented on screen.
TEST_F(BubbleViewControllerPresenterTest, TimersInitiallyNil) {
  EXPECT_EQ(nil, bubbleViewControllerPresenter_.bubbleDismissalTimer);
  EXPECT_EQ(nil, bubbleViewControllerPresenter_.engagementTimer);
}

// Tests that the timers are not |nil| once the bubble is presented on screen.
TEST_F(BubbleViewControllerPresenterTest, TimersInstantiatedOnPresent) {
  [bubbleViewControllerPresenter_
      presentInViewController:parentViewController_
                         view:parentViewController_.view
                  anchorPoint:anchorPoint_];
  EXPECT_NE(nil, bubbleViewControllerPresenter_.bubbleDismissalTimer);
  EXPECT_NE(nil, bubbleViewControllerPresenter_.engagementTimer);
}

// Tests that the bubble timer is |nil| but the engagement timer is not |nil|
// when the bubble is presented and dismissed.
TEST_F(BubbleViewControllerPresenterTest, BubbleTimerNilOnDismissal) {
  [bubbleViewControllerPresenter_
      presentInViewController:parentViewController_
                         view:parentViewController_.view
                  anchorPoint:anchorPoint_];
  [bubbleViewControllerPresenter_ dismissAnimated:NO];
  EXPECT_EQ(nil, bubbleViewControllerPresenter_.bubbleDismissalTimer);
  EXPECT_NE(nil, bubbleViewControllerPresenter_.engagementTimer);
}

// Tests that the |userEngaged| property is initially |NO|.
TEST_F(BubbleViewControllerPresenterTest, UserEngagedInitiallyNo) {
  EXPECT_FALSE(bubbleViewControllerPresenter_.isUserEngaged);
}

// Tests that the |userEngaged| property is |YES| once the bubble is presented
// on screen.
TEST_F(BubbleViewControllerPresenterTest, UserEngagedYesOnPresent) {
  [bubbleViewControllerPresenter_
      presentInViewController:parentViewController_
                         view:parentViewController_.view
                  anchorPoint:anchorPoint_];
  EXPECT_TRUE(bubbleViewControllerPresenter_.isUserEngaged);
}

// Tests that the |userEngaged| property remains |YES| once the bubble is
// presented and dismissed.
TEST_F(BubbleViewControllerPresenterTest, UserEngagedYesOnDismissal) {
  [bubbleViewControllerPresenter_
      presentInViewController:parentViewController_
                         view:parentViewController_.view
                  anchorPoint:anchorPoint_];
  EXPECT_TRUE(bubbleViewControllerPresenter_.isUserEngaged);
}
